/*++

Copyright (c) 2012 Minoca Corp.

    This file is licensed under the terms of the GNU General Public License
    version 3. Alternative licensing terms are available. Contact
    info@minocacorp.com for details. See the LICENSE file at the root of this
    project for complete licensing information.

Module Name:

    trap.S

Abstract:

    This module implements interrupt and exception trap management, such as
    saving and restoring registers.

Author:

    Evan Green 3-Jul-2012

Environment:

    Kernel mode

--*/

//
// ------------------------------------------------------------------- Includes
//

#include <minoca/kernel/x86.inc>

//
// ---------------------------------------------------------------- Definitions
//

//
// -------------------------------------------------------------------- Globals
//

//
// ----------------------------------------------------------------------- Code
//

//
// .text specifies that this code belongs in the executable section.
//
// .code32 specifies that this is 32-bit protected mode code.
//

.text
.code32

.globl HlVectorStart
.globl HlVectorEnd
.hidden HlVectorStart
.hidden HlVectorEnd

//
// VOID
// ArBreakExceptionHandlerAsm (
//     ULONG ReturnEip,
//     ULONG ReturnCodeSelector,
//     ULONG ReturnEflags
//     )
//

/*++

Routine Description:

    This routine is called directly when an debug exception occurs. It sets up
    the parameters and calls a C routine to handle the break. It then restores
    machine state to return from the exception. The arguments to this function
    are pushed by the hardware.

Arguments:

    ReturnEip - Supplies the address after the instruction that caused the trap.

    ReturnCodeSelector - Supplies the code selector the code that trapped was
        running under.

    ReturnEflags - Supplies the EFLAGS register immediately before the trap.

Return Value:

    None.

--*/

FUNCTION(ArBreakExceptionHandlerAsm)
    pushl   $0                      # Push a dummy error code.
    call    ArGenerateTrapFrame     # Create a local trap frame.
    CFI_TRAP_FRAME_PUSHED           # Set unwind info for the debugger.
    pushl   %ebx                    # Push a pointer to it as a parameter.
    call    KeDispatchBreakPointTrap  # Call the main exception handler.
    addl    $0x4, %esp              # Pop the parameter.
    call    ArRestoreTrapFrame      # Restore the trap frame
    CFI_TRAP_FRAME_POPPED           # Let the debugger know.
    addl    $4, %esp                # Pop the error code.
    iret                            # Return from the exception.

END_FUNCTION(ArBreakExceptionHandlerAsm)

//
// VOID
// KdNmiHandlerAsm (
//     VOID
//     )
//

/*++

Routine Description:

    This routine is called directly when an NMI occurs. Since it is a hardware
    task switch, no registers need to be saved.

Arguments:

    ReturnEip - Supplies the address after the instruction that caused the trap.

    ReturnCodeSelector - Supplies the code selector the code that trapped was
        running under.

    ReturnEflags - Supplies the EFLAGS register immediately before the trap.

Return Value:

    None.

--*/

FUNCTION(KdNmiHandlerAsm)
    LoadKernelDataSegments          # Load valid data segments.
    call    KeDispatchNmiTrap       # Call to the C routine to handle this mess.
    iret                            # Return from the exception.
    jmp     KdNmiHandlerAsm         # The next NMI starts here, jump back up.

END_FUNCTION(KdNmiHandlerAsm)

//
// VOID
// ArSingleStepExceptionHandlerAsm (
//     ULONG ReturnEip,
//     ULONG ReturnCodeSelector,
//     ULONG ReturnEflags
//     )
//

/*++

Routine Description:

    This routine is called directly when an debug exception occurs. It sets up
    the parameters and calls the executive to dispatch the trap.

Arguments:

    ReturnEip - Supplies the address after the instruction that caused the trap.

    ReturnCodeSelector - Supplies the code selector the code that trapped was
        running under.

    ReturnEflags - Supplies the EFLAGS register immediately before the trap.

Return Value:

    None.

--*/

FUNCTION(ArSingleStepExceptionHandlerAsm)
    pushl   $0                      # Push a dummy error code.
    call    ArGenerateTrapFrame     # Create a local trap frame.
    CFI_TRAP_FRAME_PUSHED           # Set unwind info for the debugger.
    pushl   %ebx                    # Push a pointer to it as a parameter.
    call    KeDispatchSingleStepTrap  # Call the main exception handler.
    addl    $0x4, %esp              # Pop the parameter.
    call    ArRestoreTrapFrame      # Restore the trap frame
    CFI_TRAP_FRAME_POPPED           # Let the debugger know.
    addl    $4, %esp                # Pop the error code.
    iret                            # Return from the exception.

END_FUNCTION(ArSingleStepExceptionHandlerAsm)

//
// VOID
// KdDebugServiceHandlerAsm (
//     ULONG ReturnEip,
//     ULONG ReturnCodeSelector,
//     ULONG ReturnEflags
//     )
//

/*++

Routine Description:

    This routine is entered via an IDT entry to request debug service. It sets
    up the parameters and calls KdDebugExceptionHandler, and then restores
    machine state to return from the exception. The arguments to this function
    are pushed by the hardware. Upon Entry:

        eax - Supplies the debug service request.

        ecx - Supplies the parameter to the request.

Arguments:

    ReturnEip - Supplies the address after the instruction that caused the trap.

    ReturnCodeSelector - Supplies the code selector the code that trapped was
        running under.

    ReturnEflags - Supplies the EFLAGS register immediately before the trap.

Return Value:

    None.

--*/

FUNCTION(KdDebugServiceHandlerAsm)
    pushl   $0                      # Push a dummy error code.
    call    ArGenerateTrapFrame     # Create a local trap frame.
    CFI_TRAP_FRAME_PUSHED           # Set unwind info for the debugger.
    pushl   %ebx                    # Push a pointer to the trap frame.
    call    KeDispatchDebugServiceTrap  # Call the main exception handler.
    addl    $0x4, %esp              # Pop the parameter.
    call    ArRestoreTrapFrame      # Restore the trap frame
    CFI_TRAP_FRAME_POPPED           # Let the debugger know.
    addl    $4, %esp                # Pop the error code.
    iret                            # Return from the exception.

END_FUNCTION(KdDebugServiceHandlerAsm)

//
// VOID
// ArDivideByZeroExceptionHandlerAsm (
//     ULONG ReturnEip,
//     ULONG ReturnCodeSelector,
//     ULONG ReturnEflags
//     )
//

/*++

Routine Description:

    This routine is called directly when a divide by zero exception occurs.

Arguments:

    ReturnEip - Supplies the address after the instruction that caused the trap.

    ReturnCodeSelector - Supplies the code selector the code that trapped was
        running under.

    ReturnEflags - Supplies the EFLAGS register immediately before the trap.

Return Value:

    None.

--*/

FUNCTION(ArDivideByZeroExceptionHandlerAsm)
    pushl   $0                      # Push a dummy error code.
    call    ArGenerateTrapFrame     # Create a local trap frame.
    CFI_TRAP_FRAME_PUSHED           # Set unwind info for the debugger.
    pushl   %ebx                    # Push a pointer to it as a parameter.
    call    KeDispatchDivideByZeroTrap  # Call the main exception handler.
    addl    $0x4, %esp              # Pop the parameters.
    call    ArRestoreTrapFrame      # Restore the trap frame
    CFI_TRAP_FRAME_POPPED           # Let the debugger know.
    addl    $4, %esp                # Pop the error code.
    iret                            # Return from the exception.

END_FUNCTION(ArDivideByZeroExceptionHandlerAsm)

//
// VOID
// ArFpuAccessExceptionHandlerAsm (
//     ULONG ReturnEip,
//     ULONG ReturnCodeSelector,
//     ULONG ReturnEflags
//     )
//

/*++

Routine Description:

    This routine is called directly when floating point access occurs and the
    TS bit in CR0 is

Arguments:

    ReturnEip - Supplies the address after the instruction that caused the trap.

    ReturnCodeSelector - Supplies the code selector the code that trapped was
        running under.

    ReturnEflags - Supplies the EFLAGS register immediately before the trap.

Return Value:

    None.

--*/

FUNCTION(ArFpuAccessExceptionHandlerAsm)
    pushl   $0                      # Push a dummy error code.
    call    ArGenerateTrapFrame     # Create a local trap frame.
    CFI_TRAP_FRAME_PUSHED           # Set unwind info for the debugger.
    pushl   %ebx                    # Push a pointer to it as a parameter.
    call    KeDispatchFpuAccessTrap # Call the main exception handler.
    addl    $0x4, %esp              # Pop the parameters.
    call    ArRestoreTrapFrame      # Restore the trap frame
    CFI_TRAP_FRAME_POPPED           # Let the debugger know.
    addl    $4, %esp                # Pop the error code.
    iret                            # Return from the exception.

END_FUNCTION(ArFpuAccessExceptionHandlerAsm)

//
// VOID
// ArDoubleFaultHandlerAsm (
//     VOID
//     )
//

/*++

Routine Description:

    This routine is entered via an IDT entry when a double fault exception
    occurs. Double faults are non-recoverable. This machine loops attempting
    to enter the debugger indefinitely.

Arguments:

    None.

Return Value:

    None, this routine does not return.

--*/

FUNCTION(ArDoubleFaultHandlerAsm)
    LoadKernelDataSegments         # Load valid data segments.
    call    ArpHandleDoubleFault   # Call to the C routine to handle this mess.
    nop

END_FUNCTION(ArDoubleFaultHandlerAsm)

//
// VOID
// ArProtectionFaultHandlerAsm (
//     ULONG ReturnEip,
//     ULONG ReturnCodeSelector,
//     ULONG ReturnEflags
//     )
//

/*++

Routine Description:

    This routine is called directly when a general protection fault occurs.
    It's job is to prepare the trap frame, call the appropriate handler, and
    then restore the trap frame.

Arguments:

    ReturnEip - Supplies the address after the instruction that caused the trap.

    ReturnCodeSelector - Supplies the code selector the code that trapped was
        running under.

    ReturnEflags - Supplies the EFLAGS register immediately before the trap.

Return Value:

    None.

--*/

FUNCTION(ArProtectionFaultHandlerAsm)
    call    ArGenerateTrapFrame     # Create a local trap frame.
    CFI_TRAP_FRAME_PUSHED           # Set unwind info for the debugger.
    pushl   %ebx                    # Push a pointer to it as a parameter.
    call    KeDispatchProtectionFault # Call the main handler.
    addl    $0x4, %esp              # Pop the parameter.
    call    ArRestoreTrapFrame      # Restore the trap frame
    CFI_TRAP_FRAME_POPPED           # Let the debugger know.
    addl    $4, %esp
    iret                            # Return from the exception.

END_FUNCTION(ArProtectionFaultHandlerAsm)

//
// VOID
// ArMathFaultHandlerAsm (
//     ULONG ReturnEip,
//     ULONG ReturnCodeSelector,
//     ULONG ReturnEflags
//     )
//

/*++

Routine Description:

    This routine is called directly when a x87 FPU fault occurs.

Arguments:

    ReturnEip - Supplies the address after the instruction that caused the trap.

    ReturnCodeSelector - Supplies the code selector the code that trapped was
        running under.

    ReturnEflags - Supplies the EFLAGS register immediately before the trap.

Return Value:

    None.

--*/

FUNCTION(ArMathFaultHandlerAsm)
    pushl   $0                      # Push a dummy error code.
    call    ArGenerateTrapFrame     # Create a local trap frame.
    CFI_TRAP_FRAME_PUSHED           # Set unwind info for the debugger.
    pushl   %ebx                    # Push a pointer to it as a parameter.
    call    KeDispatchMathFault     # Call the main handler.
    addl    $0x4, %esp              # Pop the parameter.
    call    ArRestoreTrapFrame      # Restore the trap frame
    CFI_TRAP_FRAME_POPPED           # Let the debugger know.
    addl    $4, %esp                # Pop dummy error code.
    iret                            # Return from the exception.

END_FUNCTION(ArMathFaultHandlerAsm)

//
// VOID
// ArTrapSystemCallHandlerAsm (
//     ULONG ReturnEip,
//     ULONG ReturnCodeSelector,
//     ULONG ReturnEflags
//     )
//

/*++

Routine Description:

    This routine is entered when the sysenter routine is entered with the TF
    flag set. It performs a normal save and sets the TF.

Arguments:

    ReturnEip - Supplies the address after the instruction that caused the trap.

    ReturnCodeSelector - Supplies the code selector the code that trapped was
        running under.

    ReturnEflags - Supplies the EFLAGS register immediately before the trap.

Return Value:

    None.

--*/

FUNCTION(ArTrapSystemCallHandlerAsm)

    //
    // ESP is currently pointing at the processor double fault stack, which
    // is also the main TSS. Switch to the thread stack.
    //

    movl    TSS_ESP0(%esp), %esp    # Load the stack.
    sti                             # Re-enable interrupts.

    //
    // Fake a user mode exception by pushing SS, Esp, Eflags, CS and Eip.
    //

    pushl   $USER_DS                # Push user mode SS.
    pushl   %eax                    # Push user ESP supplied by sysenter.
    pushfl                          # Push the Eflags.
    pushl   $USER32_CS              # Push user CS.
    pushl   %ebx                    # Push return address supplied by sysenter.
    pushl   $0                      # Push a dummy error code.
    call    ArGenerateTrapFrame     # Create a local trap frame.
    CFI_TRAP_FRAME_PUSHED           # Set unwind info for the debugger.
    orl     $IA32_EFLAG_TF, TRAP_EFLAGS(%ebx) # Set the trap flag.
    jmp     ArSystemCallHandlerAfterTrapSave  # Go to the regular path.

END_FUNCTION(ArTrapSystemCallHandlerAsm)

//
// INTN
// ArSystemCallHandlerAsm (
//     ULONG ReturnEip,
//     ULONG ReturnCodeSelector,
//     ULONG ReturnEflags
//     )
//

/*++

Routine Description:

    This routine is entered via an IDT entry to service a user mode request.
    Ecx contains the system call number, and Edx contains the argument.

Arguments:

    ReturnEip - Supplies the address after the instruction that caused the trap.

    ReturnCodeSelector - Supplies the code selector the code that trapped was
        running under.

    ReturnEflags - Supplies the EFLAGS register immediately before the trap.

Return Value:

    STATUS_SUCCESS or positive integer on success.

    Error status code on failure.

--*/

FUNCTION(ArSystemCallHandlerAsm)
    pushl   $0                      # Push a dummy error code.

    //
    // Save the complete trap frame. This routine is aware that
    // ArGenerateTrapFrame doesn't clobber ecx and edx, which hold the system
    // call number and parameter. If ArGenerateTrapFrame did clobber those
    // registers, they'd need to be reloaded after this.
    //

    call    ArGenerateTrapFrame     # Create a local trap frame.
    CFI_TRAP_FRAME_PUSHED           # Set unwind info for the debugger.

ArSystemCallHandlerAfterTrapSave:

    //
    // Push the parameters for the system call handler. The compiler is free to
    // modify the parameters on the stack in the callee, so this second push is
    // necessary.
    //

    subl    $4, %esp                # Make room for the boolean.
    pushl   %esp                    # Push the boolean pointer parameter.
    pushl   %ebx                    # Push a pointer to the trap frame.
    pushl   %edx                    # Push system call parameter.
    pushl   %ecx                    # Push system call number.
    CFI_ADJUST_CFA_OFFSET(20)       # Tell the debugger.
    call    KeSystemCallHandler     # Call the main exception handler.
    addl    $0x10, %esp             # Pop the parameters.
    CFI_ADJUST_CFA_OFFSET(-16)      # Tell the debugger.
    movl    %eax, TRAP_EAX(%ebx)    # Save the return value in the trap frame.

    //
    // See if there is a signal pending on the current thread, as returned by
    // the system call handler.
    //

    popl    %ecx                    # Get the signal pending boolean
    CFI_ADJUST_CFA_OFFSET(-4)       # Tell the debugger.
    testl   %ecx, %ecx              # See if it's zero.
    jz      ArSystemCallHandlerExit # Skip the expensive signal stuff.

    //
    // Push the trap frame as the first parameter. The system call number and
    // system call parameter were saved on the stack earlier for this call.
    //

    pushl   %ebx                    # Push trap frame.
    CFI_ADJUST_CFA_OFFSET(4)        # Notify about the push.
    call    PsApplyPendingSignalsOrRestart  # Apply signals or restart the call.
    addl    $0x4, %esp              # Pop the trap frame.
    CFI_ADJUST_CFA_OFFSET(-4)       # Notify about the pop.

ArSystemCallHandlerExit:
    call    ArRestoreTrapFrame      # Restore the trap frame
    CFI_TRAP_FRAME_POPPED           # Let the debugger know.
    addl    $0x4, %esp              # Pop the error code.
    iret                            # Return from the exception.

END_FUNCTION(ArSystemCallHandlerAsm)

//
// INTN
// ArSysenterHandlerAsm (
//     VOID
//     )
//

/*++

Routine Description:

    This routine is executed when user mode invokes the SYSENTER instruction.
    Upon entry, CS, EIP, and ESP are set to predefined values set in MSRs.

Arguments:

    None.

Return Value:

    STATUS_SUCCESS or positive integer on success.

    Error status code on failure.

--*/

FUNCTION(ArSysenterHandlerAsm)

    //
    // ESP is currently pointing at the processor double fault stack, which
    // is also the main TSS. Switch to the thread stack. Make room on the stack
    // as if the hardware has pushed SS, ESP, EFLAGS, CS, EIP, and an error
    // code, since the return path might go out with a full iret.
    //

    movl    TSS_ESP0(%esp), %esp    # Load the stack.
    subl    $24, %esp               # Fake the hardware pushes.

    //
    // Make a fake trap frame but fill in the bare minimum: Eip and Esp.
    // These are needed so they can be saved if a signal is dispatched.
    //

    pushl   %eax                    # Save user ESP in trap frame.
    LoadKernelDataSegments          # Load kernel data segments (using EAX).
    sti                             # Re-enable interrupts.
    subl    $(TRAP_FRAME_SIZE - 4), %esp  # Allocate the rest of the trap frame.
    movl    %ebx, TRAP_EIP(%esp)    # EBX contains the return address.
    movl    %esp, %ebx              # Save the trap frame in EBX.
    movl    %ecx, TRAP_ECX(%esp)    # Save system call number for restarts.
    movl    %edx, TRAP_EDX(%esp)    # Save system call parameter for restarts.

    //
    // There's a trap frame at ebx, which is nice since it won't require
    // maintenance over all these upcoming push/pops of the stack.
    //

    CFI_DEF_CFA(%ebx, 0)
    CFI_OFFSET(%eip, TRAP_EIP)
    CFI_OFFSET(%esp, TRAP_ESP)
    CFI_OFFSET(%ecx, TRAP_ECX)
    CFI_OFFSET(%edx, TRAP_EDX)

    //
    // Move user DS (rather than user CS) into the trap frame to indicate
    // 1) this is a user mode trap frame and 2) it's incomplete.
    //

    movl    $USER_DS, TRAP_CS(%ebx) # Indicate a user mode trap frame.

    //
    // Push the parameters for the system call handler and execute it.
    //

    subl    $4, %esp                # Make room for the boolean.
    pushl   %esp                    # Push the boolean pointer parameter.
    pushl   %ebx                    # Push a pointer to the sort-of trap frame.
    pushl   %edx                    # Push EDX, the system call parameter.
    pushl   %ecx                    # Push ECX, the system call number.
    call    KeSystemCallHandler     # Call out to the main service handler.
    addl    $0x10, %esp             # Pop the function parameters.

    //
    // See if there is a signal pending on the current thread, as returned by
    // the system call handler.
    //

    popl    %ecx                    # Get the signal pending boolean
    testl   %ecx, %ecx              # See if it's zero.
    jz      ArSysenterHandlerRestore    # Skip the expensive signal stuff.

    //
    // See if the trap frame is already complete, as a result perhaps of the
    // system call itself. If so, skip the full save part, as it might undo
    // useful work (like restoring pre-signal context).
    //

    cmpl    $USER32_CS, TRAP_CS(%ebx)   # See if the trap frame is complete.
    je      ArSysenterSignalDispatch    # Skip the save if complete already.

    //
    // Upgrade the trap frame to a complete one. Update user CS to indicate the
    // trap frame is complete. This will also mean the expensive iret exit path
    // is used.
    //

    movl    $USER32_CS, TRAP_CS(%ebx)  # Save CS.
    movl    $USER_DS, TRAP_DS(%ebx)    # Save DS.
    movl    $USER_DS, TRAP_ES(%ebx)    # Save ES.
    movl    $GDT_THREAD, TRAP_FS(%ebx) # Save FS.
    movl    $GDT_THREAD, TRAP_GS(%ebx) # Save GS.
    movl    $USER_DS, TRAP_SS(%ebx)    # Save SS.

    //
    // Save the registers.
    //

    movl    %eax, TRAP_EAX(%ebx) # Save the return value in the trap frame.
    movl    $0x0, TRAP_EBX(%ebx) # Zero EBX. It holds the kernel trap pointer.
    movl    %esi, TRAP_ESI(%ebx) # Save ESI. It is non-volatile.
    movl    %edi, TRAP_EDI(%ebx) # Save EDI. It is non-volatile.
    movl    %ebp, TRAP_EBP(%ebx) # Save EBP. It is non-volatile.
    movl    $0x0, TRAP_ERRORCODE(%ebx) # Scrub the error code.
    movl    $(IA32_EFLAG_ALWAYS_1 | IA32_EFLAG_IF), TRAP_EFLAGS(%ebx)

ArSysenterSignalDispatch:

    //
    // Push the trap frame as the first parameter, either apply signals now
    // that a full trap frame is available, or potentially restart the system
    // call if no signals were applied (maybe a process-wide signal got
    // serviced by another thread).
    //

    pushl   %ebx                    # Pass the trap frame.
    call    PsApplyPendingSignalsOrRestart  # Dispatch signals or restart.
    addl    $0x4, %esp              # Pop the parameter.

ArSysenterHandlerRestore:

    //
    // See if the trap frame is complete. If it is, then go do the slower
    // complete restore.
    //

    cmpl    $USER32_CS, TRAP_CS(%ebx)   # See if the trap frame is complete.
    je      ArSystemCallHandlerExit     # Do a full restore if so.

    //
    // Reset the segment registers to user mode and return.
    //

    mov     $USER_DS, %cx           # Get the user mode DS.
    mov     %cx, %ds                # Move to DS.
    mov     %cx, %es                # Move to ES.
    mov     $GDT_THREAD, %cx        # Get the user-mode GS.
    mov     %cx, %fs                # Move to FS.
    mov     %cx, %gs                # Move to GS.
    CFI_DEF_CFA(%esp, 0)            # The trap frame is at ESP, EBX is gone.

    //
    // Restore some portions of the pseudo trap frame. Do not zero EAX as it
    // holds the return value from the system call.
    //

    movl    TRAP_ESP(%esp), %ecx    # Sysexit moves ECX to ESP.
    movl    TRAP_EIP(%esp), %edx    # Sysexit moves EDX to EIP.
    sysexit                         # Return to user mode, slickly.

END_FUNCTION(ArSysenterHandlerAsm)

//
// VOID
// ArpPageFaultHandlerAsm (
//     ULONG ReturnEip,
//     ULONG ReturnCodeSelector,
//     ULONG ReturnEflags
//     )
//

/*++

Routine Description:

    This routine is called directly when a page fault occurs.

Arguments:

    ReturnEip - Supplies the address after the instruction that caused the
        fault.

    ReturnCodeSelector - Supplies the code selector the code that faulted was
        running under.

    ReturnEflags - Supplies the EFLAGS register immediately before the fault.

Return Value:

    None.

--*/

FUNCTION(ArpPageFaultHandlerAsm)
    call    ArGenerateTrapFrame     # Create a local trap frame.
    CFI_TRAP_FRAME_PUSHED           # Set unwind info for the debugger.
    movl    %cr2, %ecx              # Get the faulting address.
    xor     %edx, %edx              # Zero edx.
    movl    %edx, %cr2              # Clear CR2.
    sti                             # Re-enable interrupts.
    pushl   %ebx                    # Push a pointer to to the trap frame.
    pushl   %ecx                    # Push CR2.
    CFI_ADJUST_CFA_OFFSET(8)        # Tell the debugger.
    call    KeDispatchPageFault     # Call the main exception handler.
    addl    $8, %esp                # Pop the parameters.
    CFI_ADJUST_CFA_OFFSET(-8)       # Pop them in the debugger.
    call    ArRestoreTrapFrame      # Restore the trap frame
    CFI_TRAP_FRAME_POPPED           # Let the debugger know.
    addl    $4, %esp                # Pop the error code.
    iret                            # Return from the exception.

END_FUNCTION(ArpPageFaultHandlerAsm)

//
// VOID
// HlSpuriousInterruptHandlerAsm (
//     ULONG ReturnEip,
//     ULONG ReturnCodeSelector,
//     ULONG ReturnEflags
//     )
//

/*++

Routine Description:

    This routine handles spurious interrupts. It does not require an EOI or
    other interrupt acknowledgement.

Arguments:

    ReturnEip - Supplies the address after the instruction that caused the trap.

    ReturnCodeSelector - Supplies the code selector the code that trapped was
        running under.

    ReturnEflags - Supplies the EFLAGS register immediately before the trap.

Return Value:

    None.

--*/

FUNCTION(HlSpuriousInterruptHandlerAsm)
    pushl   $0                      # Push a dummy error code.
    call    ArGenerateTrapFrame     # Create a local trap frame.
    CFI_TRAP_FRAME_PUSHED           # Set unwind info for the debugger.
    addl    $1, HlSpuriousInterruptCount      # Count interrupts
    call    ArRestoreTrapFrame      # Restore the trap frame
    CFI_TRAP_FRAME_POPPED           # Let the debugger know.
    addl    $4, %esp                # Pop the error code.
    iret                            # Return from the exception.

END_FUNCTION(HlSpuriousInterruptHandlerAsm)

//
// VOID
// ArRestoreTrapFrame (
//     PTRAP_FRAME TrapFrame
//     )
//

/*++

Routine Description:

    This routine restores information contained in a trap frame to the
    processor and prepares the machine for an iret back to the code that
    generated this trap frame. It's not really a function because it assumes
    a specific stack layout and modifies data that technically belongs to the
    caller. It should only be called immediately before returning from an
    exception or interrupt.

Arguments:

    TrapFrame - Supplies the trap frame to restore, in ebx.

Return Value:

    Upon return, the trap frame will have been popped off the stack, and the
    machine will be in the same state as right after the exception happened.

--*/

FUNCTION(ArRestoreTrapFrame)
    mov     %ebx, %ecx                      # Move trap frame to ecx.
    addl    $TRAP_FRAME_SIZE+16, %ebx       # Compute the pre-exception stack.
    movl    TRAP_CS(%ecx), %edx             # Get the destination CS.

//
// The exception is returning to either kernel or user mode. Either way restore
// the common data segment registers. Hold off on DS, as this routine will
// make a couple more DS: accesses. Save it in ESI.
//

    movl    TRAP_DS(%ecx), %esi             # Save DS into ESI for now.
    movl    TRAP_ES(%ecx), %eax             # Restore ES.
    movw    %ax, %es                        #
    movl    TRAP_FS(%ecx), %eax             # Restore FS.
    movw    %ax, %fs                        #
    movl    TRAP_GS(%ecx), %eax             # Restore GS.
    movw    %ax, %gs                        #

//
// Restore the remaining registers based on the destination mode.
//

    movl    %edx, %eax                      # Get CS (loaded above).
    andl    $SEGMENT_PRIVILEGE_MASK, %eax   # AND out the privilege.
    jz      RestoreTrapFrameToKernelMode    # Jump over if not.

//
// The exception is going to jump back into user mode, so put the stack
// pointer and segments back into the exception-generated part of the stack.
//

    movl    TRAP_ESP(%ecx), %eax            # Restore Esp.
    movl    %eax, TRAP_RET_ESP(%ecx)        #
    movl    TRAP_SS(%ecx), %eax             # Restore SS.
    movl    %eax, TRAP_RET_SS(%ecx)         #
    jmp     RestoreTrapFrameGeneralRegisters        # Jump to the end.

RestoreTrapFrameToKernelMode:

//
// The exception came from kernel mode, so restore the stack segment register.
//

    movl    TRAP_SS(%ecx), %eax      # Restore SS. If this doesn't allow access
    movw    %ax, %ss                 # to the current stack, this will be bad.

//
// Build the iret return. The parameters going on the new stack are Ebx, Return
// Address, Error Code, Eip, CS, and Eflags.
//
// Note that if the stack pointer doesn't change, the Ebx and Return address
// values destroy data that was on the stack there (immediately after the
// Eflags, CS, Eip). This happens to be the last two values in the trap frame
// structure. Luckily those members are Esp and Eflags, which are restored
// immediately before their values are destroyed.
//

    movl    TRAP_ESP(%ecx), %ebx     # Get the kernel Esp.

RestoreTrapFrameGeneralRegisters:
    subl    $24, %ebx                # Make room for the new parameters.
    movl    TRAP_EIP(%ecx), %eax     # Restore Eip.
    movl    %eax, 12(%ebx)           #
    movl    %edx, 16(%ebx)           # Restore CS.
    movl    TRAP_EFLAGS(%ecx), %eax  # Restore Eflags.
    movl    %eax, 20(%ebx)           #
    movl    TRAP_EBX(%ecx), %eax     # Save Ebx.
    movl    %eax, (%ebx)             #
    movl    (%esp), %eax             # Save this function's return address.
    movl    %eax, 4(%ebx)            #

//
// Now that all DS: accesses are finished, restore DS.
//

    movw    %si, %ds

//
// Move the trap frame pointer to the stack, popping everything up until then,
// including the extended state.
//

    movl    %ecx, %esp               # Pop up to the trap frame.

//
// Restore the general registers.
//

    movl    TRAP_EAX(%esp), %eax     #
    movl    TRAP_ECX(%esp), %ecx     #
    movl    TRAP_EDX(%esp), %edx     #
    movl    TRAP_ESI(%esp), %esi     #
    movl    TRAP_EDI(%esp), %edi     #
    movl    TRAP_EBP(%esp), %ebp     #

//
// Transition to the new kernel mode stack pointer, pop Ebx, and return.
//

    movl    %ebx, %esp              # Move stacks!
    popl    %ebx                    # Restore Ebx.
    ret

END_FUNCTION(ArRestoreTrapFrame)

//
// --------------------------------------------------------- Internal Functions
//

//
// This macro stamps out the assembly dispatch code necessary for interrupts
// received at each vector. It will create code for all vectors between
// MinimumVector and MaximumVector.
//

.macro InterruptVector _Vector

    //
    // 0x6A xx is the instruction for push imm8, except the immediate is sign
    // extended. The assembler will use the longer form for numbers >= 0x80
    // since those should not be sign extended. Use the shorter form directly
    // here to save space, and deal with it using a cast in the C code.
    //

    .byte   0x6A
    .byte   (\_Vector)
    jmp     KeInterruptEntry

.endm

.macro InterruptVectors16 _Vector
    InterruptVector (\_Vector)
    InterruptVector (\_Vector + 1)
    InterruptVector (\_Vector + 2)
    InterruptVector (\_Vector + 3)
    InterruptVector (\_Vector + 4)
    InterruptVector (\_Vector + 5)
    InterruptVector (\_Vector + 6)
    InterruptVector (\_Vector + 7)
    InterruptVector (\_Vector + 8)
    InterruptVector (\_Vector + 9)
    InterruptVector (\_Vector + 10)
    InterruptVector (\_Vector + 11)
    InterruptVector (\_Vector + 12)
    InterruptVector (\_Vector + 13)
    InterruptVector (\_Vector + 14)
    InterruptVector (\_Vector + 15)

.endm

//
// Now actually instantiate the macro to create the vector code.
//

HlVectorStart:

InterruptVectors16 0x30
InterruptVectors16 0x40
InterruptVectors16 0x50
InterruptVectors16 0x60
InterruptVectors16 0x70
InterruptVectors16 0x80
InterruptVectors16 0x90
InterruptVectors16 0xA0
InterruptVectors16 0xB0
InterruptVectors16 0xC0
InterruptVectors16 0xD0
InterruptVectors16 0xE0
InterruptVectors16 0xF0

HlVectorEnd:

//
// PTRAP_FRAME
// ArGenerateTrapFrame (
//     ULONG ReturnEip,
//     ULONG ReturnCs,
//     ULONG ReturnEflags,
//     ...
//     )
//

/*++

Routine Description:

    This routine generates a trap frame based on the data pushed onto the
    stack by the processor after an exception. It is not really a function
    in that it assumes a certain stack layout and will modify data that
    belongs to the caller. This function should only be called immediately
    after an interrupt/exception.

Arguments:

    ReturnEip - Supplies the instruction that generated the exception.

    ReturnCs - Supplies the code selector of the code that generated the
        exception.

    ReturnEflags - Supplies the flags of the code that generated the
        exception.

Return Value:

    Returns a pointer to the trap frame in ebx.

--*/

FUNCTION(ArGenerateTrapFrame)

//
// Allocate room on the stack for the trap frame plus the return address,
// minus the original return address.
//

    subl    $TRAP_FRAME_SIZE, %esp  #
    pushl   %eax                    # Save eax for a moment while the return
    movl    TRAP_FRAME_SIZE+4(%esp), %eax     # address is moved.
    movl    %eax, 4(%esp)           #
    popl    %eax                    # Restore eax
    movl    %eax, TRAP_EAX+4(%esp)  # Save the general registers.
    movl    %ebx, TRAP_EBX+4(%esp)  #
    movl    %ecx, TRAP_ECX+4(%esp)  #
    movl    %edx, TRAP_EDX+4(%esp)  #
    movl    %esi, TRAP_ESI+4(%esp)  #
    movl    %edi, TRAP_EDI+4(%esp)  #
    movl    %ebp, TRAP_EBP+4(%esp)  #
    movl    TRAP_RET_ERRORCODE+4(%esp), %eax  # Save the error code.
    movl    %eax, TRAP_ERRORCODE+4(%esp)      #
    movl    TRAP_RET_EIP+4(%esp), %eax        # Save the return address.
    movl    %eax, TRAP_EIP+4(%esp)            #
    movl    TRAP_RET_CS+4(%esp), %ebx         # Save the return CS.
    movl    %ebx, TRAP_CS+4(%esp)             #
    movl    TRAP_RET_EFLAGS+4(%esp), %eax     # Save eflags.
    movl    %eax, TRAP_EFLAGS+4(%esp)

//
// Figure out if a ring change occurred.
//

    andl    $SEGMENT_PRIVILEGE_MASK, %ebx   # exception had a ring change.
    jz      GenerateTrapFrameFromKernelMode # Jump over if not.

//
// The exception caused a privilege level change, so the stack contains the
// following ULONGs: Eip, CS, Eflags, Esp, and SS. Get the other segment
// selectors from their current values.
//

    movl    TRAP_RET_ESP+4(%esp), %eax      # Save Esp.
    movl    %eax, TRAP_ESP+4(%esp)          #
    movl    TRAP_RET_SS+4(%esp), %eax       # Save SS.
    movl    %eax, TRAP_SS+4(%esp)           #
    xorl    %eax, %eax                      # Zero out eax.
    movw    %ds, %ax                        # Save DS.
    movl    %eax, TRAP_DS+4(%esp)           #
    movw    %es, %ax                        # Save ES.
    movl    %eax, TRAP_ES+4(%esp)           #
    movw    %fs, %ax                        # Save FS.
    movl    %eax, TRAP_FS+4(%esp)           #
    movw    %gs, %ax                        # Save GS.
    movl    %eax, TRAP_GS+4(%esp)           #
    jmp     GenerateTrapFrameEnd            # All done.

GenerateTrapFrameFromKernelMode:

//
// The exception came from kernel mode, so the only things pushed on the stack
// by the processor are Eip, CS, and Eflags. The data segments also don't need
// to be saved. Get the data segments from their current values. Since there
// was no stack change, the Esp is simply this current one except all the
// stuff pushed by the exception, plus the error code.
//

    movl    %esp, %eax                      # Save Esp.
    addl    $TRAP_FRAME_SIZE+20, %eax       # Remove exception stack items.
    movl    %eax, TRAP_ESP+4(%esp)          #
    xorl    %eax, %eax                      # Zero out eax.
    movw    %ds, %ax                        # Save DS.
    movl    %eax, TRAP_DS+4(%esp)           #
    movw    %es, %ax                        # Save ES.
    movl    %eax, TRAP_ES+4(%esp)           #
    movw    %fs, %ax                        # Save FS.
    movl    %eax, TRAP_FS+4(%esp)           #
    movw    %gs, %ax                        # Save GS.
    movl    %eax, TRAP_GS+4(%esp)           #
    movw    %ss, %ax                        # Save SS.
    movl    %eax, TRAP_SS+4(%esp)           #

GenerateTrapFrameEnd:
    LoadKernelDataSegments                  # Load valid data segments.
    popl    %edi                            # Pop the return address.
    movl    %esp, %ebx                      # Return the trap pointer.
    jmp     *%edi                           # Return

END_FUNCTION(ArGenerateTrapFrame)

//
// Define the common interrupt entry code. At this point the vector number has
// been pushed into the error code slot, but nothing else has been done. Note
// that this code needs to be far enough away from the vectors themselves so
// that none of the jumps in the vectors turn into shorter instructions
// (distance >= 0x100).
//

FUNCTION(KeInterruptEntry)
    call    ArGenerateTrapFrame     # Create a local trap frame.
    CFI_TRAP_FRAME_PUSHED           # Set unwind info for the debugger.
    pushl   %ebx                    # Push a pointer to it as a parameter.
    CFI_ADJUST_CFA_OFFSET(4)
    call    KeDispatchInterrupt     # Dispatch the interrupt.
    addl    $4, %esp                # Pop the parameters.
    CFI_ADJUST_CFA_OFFSET(-4)
    call    ArRestoreTrapFrame      # Restore state.
    CFI_TRAP_FRAME_POPPED           # Let the debugger know.
    addl    $4, %esp                # Pop the error code.
    CFI_OFFSET(%eip, 0)             # The return address is right there.
    iret                            # Return from the exception.

END_FUNCTION(KeInterruptEntry)

